package com.hup.utils.io.poi.excel;

import lombok.extern.log4j.Log4j2;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.SXSSFSheet;

import java.util.Date;

/**
 * @author hugan
 * @date 2019/10/27
 */
@Log4j2
public class WbUtil {

    /**
     * 实现某个sheet为激活状态
     *
     * @apiNote wb.setActiveSheet() 有bug,要手工修改ActiveSheet属性(否则打开文件后,底部的 第一个sheet标签 始终是选中状态的);
     * 使用官网的example也能重新bug
     */
    public static void activeSheet(Sheet sheet) {
        int activeSheetIndex = sheet.getWorkbook().getActiveSheetIndex();
        sheet.getWorkbook().getSheetAt(activeSheetIndex).setSelected(false);
        int sheetIndex = sheet.getWorkbook().getSheetIndex(sheet);
        sheet.getWorkbook().setActiveSheet(sheetIndex);
    }

    /**
     * 冻结第一行
     */
    public static void freezeFirstRow(Sheet sheet) {
        sheet.createFreezePane(0, 1, 0, 1);
    }

    /**
     * 启用首行自动筛选;
     * @param sheet 如果是SXSSFSheet,应该使用 {@link #setAutoFilter(Sheet, int, int)}
     */
    public static AutoFilter setAutoFilter(Sheet sheet) {
        int rIndex = 0;
        Row row;
        while ((row = sheet.getRow(rIndex)) == null) {
            rIndex++;
            if (rIndex >= 10) {
                //SXSSFSheet在超出缓存范围后,getRow是会返回null的
                boolean isSXSSFSheet = sheet instanceof SXSSFSheet;
                log.error("表单[{}]前10行没有数据{},设置[自动筛选]失败", sheet.getSheetName(),
                        isSXSSFSheet ? ",是SXSSFSheet(请考虑缓存范围)" : "");
                return null;
            }
        }
        short firstCellNum = row.getFirstCellNum();
        short lastCellNum = row.getLastCellNum();
        return setAutoFilter(sheet, firstCellNum, lastCellNum - 1);
    }

    /**
     * 启用首行自动筛选;
     * 由于{@link AutoFilter}的todo一直没实现,所以只支持启用,不支持增加筛选条件等
     */
    public static AutoFilter setAutoFilter(Sheet sheet, int firstColumn, int lastColumn) {
        CellRangeAddress range = new CellRangeAddress(0, 0, firstColumn, lastColumn);

        //AutoFilter从Aug 2010开始,只提供了接口,没有方法;
        AutoFilter autoFilter = sheet.setAutoFilter(range);
        int methodLen = AutoFilter.class.getMethods().length;
        if (methodLen > 0) {
            throw new IllegalArgumentException("喜庆!这个类被升级了");
        }
        return autoFilter;
    }

    /**
     * 设置自适应列宽;
     * @apiNote 未测试SXSSFSheet是否需要在createSheet后马上声明;
     * @param column 起始于0
     */
    public static void autoSizeColumn(Sheet sheet, int column) {
        if (sheet instanceof SXSSFSheet) {
            //SXSSFSheet要特殊处理
            ((SXSSFSheet) sheet).trackColumnForAutoSizing(column);
        }
        sheet.autoSizeColumn(column);
    }

    /**
     * 设置换行的文字内容(会导致单行内容超过列宽时也会换行);
     *
     * @apiNote 如果某整列需要换行, 则不能使用此方法,
     * 应使用 {@link #setMultiRowText(Cell, String, CellStyle)},
     * 因为cellStyle,是不能无限创建的(也会增大文件大小),如果频繁创建,会导致后面的单元格不能成功设置style
     */
    public static void setMultiRowText(Cell cell, String str) {
        CellStyle oldStyle = cell.getCellStyle();
        CellStyle newStyle = cell.getSheet().getWorkbook().createCellStyle();
        newStyle.cloneStyleFrom(oldStyle);
        setMultiRowText(cell, str, newStyle);
    }

    /**
     * 设置换行的文字内容(会导致单行内容超过列宽时也会换行)
     */
    public static void setMultiRowText(Cell cell, String str, CellStyle wrapTextStyle) {
        wrapTextStyle.setWrapText(true);//需要设置换行才可以!
        cell.setCellStyle(wrapTextStyle);
        cell.setCellValue(new HSSFRichTextString(str));
    }

    /**
     * 重置行高为默认一行的高度
     */
    public static void resetRowHeight(Row row) {
        row.setHeight((short) 280);//高度是多次尝试得到的
    }

    /**
     * 根据名称模糊获取sheet
     *
     * @return notNull,如果有多个符合的sheet则只会返回靠后的一个;
     * @throws IllegalArgumentException 找不到表单
     */
    public static Sheet getSheetLikeName(Workbook wb, String name) {
        for (int i = wb.getNumberOfSheets() - 1; i >= 0; i--) {
            //看源码得,先getName是没绕弯的
            String sheetName = wb.getSheetName(i);
            if (sheetName.contains(name)) {
                return wb.getSheetAt(i);
            }
        }
        throw new IllegalArgumentException("找不到[" + name + "]表单");
    }

    /**
     * 获取单元格值;如果是公式,返回公式结果
     *
     * @param cell      nullable
     * @param evaluator 公式执行器: FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator();
     * @throws IllegalArgumentException 未知单元格类型
     */
    @SuppressWarnings({"deprecation", "unchecked"})
    public static <T extends Object> T getCellValue(Cell cell, FormulaEvaluator evaluator) {
        if (cell == null) return null;

        switch (cell.getCellType()) {
            case NUMERIC://0
                if (HSSFDateUtil.isCellDateFormatted(cell)) {
                    return (T) HSSFDateUtil.getJavaDate(cell.getNumericCellValue());
                }
                return (T) (Double) cell.getNumericCellValue();
            case STRING://1
                return (T) cell.getStringCellValue();
            case FORMULA://2
                if (evaluator != null)
                    return (T) getCellValue(evaluator.evaluate(cell));
                return (T) cell.getCellFormula();
            case BOOLEAN://4
                return (T) (Boolean) cell.getBooleanCellValue();
            case BLANK://3
            case ERROR://5
                return null;
            default:
                throw new IllegalArgumentException("未知单元格类型:" + cell.getCellType());
        }
    }

    /**
     * 根据cv获取值,按需可以是public
     */
    private static Object getCellValue(CellValue cv) {
        switch(cv.getCellType()) {
            case NUMERIC :
                return cv.getNumberValue();
            case STRING:
                return cv.getStringValue();
            case BOOLEAN:
                return cv.getBooleanValue();
            case ERROR:
                return ErrorEval.getText(cv.getErrorValue());
        }
        return null;
    }

    /**
     * 为cell设置值,处理null Number等情况;不适用于公式
     * @apiNote 仅适用于固定值设置??原有旧注释
     * @param v nullable
     */
    public static Cell setCellValue(Object v, Cell cell) {
        if (v == null) {
            cell.setCellValue((String) null);
            return cell;
        }

        if (v instanceof Integer) {
            cell.setCellValue((int) v);
        }
        else if (v instanceof Number) {
            cell.setCellValue(((Number) v).doubleValue());
        }
        else if (v instanceof Boolean) {
            cell.setCellValue((boolean) v);
        }
        else if (v instanceof Date) {
            cell.setCellValue((Date) v);
        }
        else if (v instanceof String) {
            cell.setCellValue((String) v);
        }
        else {
            throw new RuntimeException("不支持类型:" + v.getClass() + "," + v);
        }
        return cell;
    }

}
